package net

import (
	"context"
	"errors"
	"gitee.com/dennis-kk/service-box-go/util/slog"
	"github.com/panjf2000/gnet"
	"sync"
)

type listenMap map[string]bool

//gnetLoop gnet framework wrapper
type gnetLoop struct {
	srvAddrs  listenMap     //server address cache !
	connector IBoxConnector // client
	sEH       *serverEH     //server event handle
	cEH       *clientEH     //client event handle
	netMQ     NetEventQueue // send event to box
	loopCh    NetEventQueue // send event to loop
	signal    chan struct{} // notify single
	wg        sync.WaitGroup
}

func makeGnetLoop(v interface{}) IBoxNetLoop {

	if mq, ok := v.(NetEventQueue); ok {
		return &gnetLoop{
			srvAddrs:  make(listenMap),
			connector: nil,
			sEH:       nil,
			cEH:       nil,
			netMQ:     mq,
			loopCh:    make(NetEventQueue, 16),
			signal:    make(chan struct{}),
			wg:        sync.WaitGroup{},
		}
	}
	return nil
}

//Start create gnet instance
func (gl *gnetLoop) Start() error {
	if gl == nil {
		//TODO add error log and replace with common error
		return nil
	}

	gl.sEH = makeServerEH(gl)
	if gl.sEH == nil {
		return nil
	}

	gl.cEH = makeClientEH(gl)
	if gl.cEH == nil {
		//TODO add error
		return nil
	}

	gl.connector = BoxConnector(gl.cEH)
	if err := gl.connector.Start(); err != nil {
		return err
	}
	gl.wg.Add(1)

	//start goroutine
	go gl.loop()

	// wait for goroutine start really
	<-gl.signal

	return nil
}

func (gl *gnetLoop) Stop() error {
	if gl == nil {
		return nil
	}

	ctx := context.Background()
	for k := range gl.srvAddrs {
		err := gnet.Stop(ctx, k)
		if err != nil {
			continue
		}
	}
	gl.signal <- struct{}{}
	close(gl.signal)
	gl.wg.Wait()

	close(gl.loopCh)

	var err error
	if err = gl.connector.Stop(); err != nil {
		//TODO add some log
		slog.Error("[ServiceBox] connector stop error !")
	}

	return err
}

func (gl *gnetLoop) Tick() {
}

func (gl *gnetLoop) loop() {
	defer func() {
		gl.wg.Done()
	}()

	//notify goroutine start
	gl.signal <- struct{}{}

	for {
		select {
		case event := <-gl.loopCh:
			if event != nil {
				gl.doNetReq(event)
			}

		// 等待关闭
		case <-gl.signal:
			return
		}
	}
}

func (gl *gnetLoop) Notify(data *EventData) {
	if gl == nil || gl.netMQ == nil {
		return
	}
	gl.netMQ <- data
}
func (gl *gnetLoop) SendToLoop(data *EventData) {
	gl.loopCh <- data
}

func (gl *gnetLoop) doNetReq(event *EventData) {
	switch event.NetType {
	case EventReqConnect:
		gl.doConnect(event)
	}
}

func (gl *gnetLoop) ConnectTo(network, address string) (IBoxConn, error) {
	if gl == nil || gl.connector == nil {
		//TODO add log and print error !
		return nil, errors.New("invalid connector")
	}
	return gl.connector.ConnectTo(network, address)
}

func (gl *gnetLoop) ListenAt(network, address string) error {
	if gl == nil {
		return nil
	}

	protoAddr := network + "://" + address
	if _, ok := gl.srvAddrs[protoAddr]; ok {
		//TODO add error log
		return nil
	}

	gl.srvAddrs[protoAddr] = true
	gl.wg.Add(1)

	signal := make(chan struct{})

	go func() {
		defer gl.wg.Done()
		//notify goroutine start
		signal <- struct{}{}
		err := gnet.Serve(gl.sEH, protoAddr, gnet.WithReuseAddr(true), gnet.WithTCPKeepAlive(0), gnet.WithTCPNoDelay(gnet.TCPNoDelay))
		if err != nil {
			slog.Error("Stop Server Listener %s error %v ", protoAddr, err)
			return
		}
	}()

	//wait for goroutine start
	<-signal

	return nil
}

func (gl *gnetLoop) doConnect(event *EventData) {
	ctEvent := event.Data.(*ConnectToEvent)

	nEvent := GetEventData()
	nEvent.NetType = EventRespConnect
	conn, err := gl.ConnectTo(ctEvent.Network, ctEvent.Addr)
	nEvent.Data = &ConnectEvent{
		Host: ctEvent.Addr,
		Conn: conn,
	}
	nEvent.Err = err
	gl.Notify(nEvent)
}
